package com.nus.fellowmark.modules.assignment.service.impl;

import com.nus.fellowmark.dao.PairingDao;
import com.nus.fellowmark.dao.SubmissionDao;
import com.nus.fellowmark.entity.PairingEntity;
import com.nus.fellowmark.entity.SubmissionEntity;
import com.nus.fellowmark.modules.assignment.dto.PairingDTO;
import com.nus.fellowmark.modules.assignment.service.PairingService;
import com.nus.fellowmark.modules.assignment.vo.PairingVO;
import com.nus.fellowmark.modules.module.service.EnrollmentService;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.nus.fellowmark.common.utils.PageUtils;
import com.nus.fellowmark.common.utils.Query;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;


@Service("pairingService")
public class PairingServiceImpl extends ServiceImpl<PairingDao, PairingEntity> implements PairingService {

    @Autowired
    EnrollmentService enrollmentService;

    @Autowired
    PairingDao pairingDao;

    @Autowired
    SubmissionDao submissionDao;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<PairingEntity> page = this.page(
                new Query<PairingEntity>().getPage(params),
                new QueryWrapper<PairingEntity>()
        );

        return new PageUtils(page);
    }

    // TODO: are all assignments individual?
    @Transactional
    @Override
    public Boolean pair(PairingDTO pairingDTO) {
        Integer assignmentId = pairingDTO.getAssignmentId();
        Integer pairSize = pairingDTO.getPairSize();
        // 1. get moduleId
        Integer moduleId = pairingDTO.getModuleId();

        // 2. get studentIds in the module
        List<Integer> studentIds = enrollmentService.getStudentIdsInModule(moduleId);
        // 3. for each student, loop [pairSize] pairs, randomly pick [pairSize] markers, save into database
        Deque<Marker> queue = new ArrayDeque<>();
        // 3.1 init marker list, shuffle the list to make sure to pick randomly
        List<Integer> markerIds = new ArrayList<>(studentIds);
        Collections.shuffle(markerIds);
        markerIds.forEach(markerId -> {
            queue.add(new Marker(markerId, pairSize));
        });

        // 3.2 students get n markers from the queue,
        //      poll a marker from the queue
        //      if markerId == studentId, push it back to the queue.
        //      else count--, new PairingEntity(), save pairEntity to db.
        //          then push it back to the queue if marker.count > 0
        List<PairingEntity> pairs = new ArrayList<>();
        for (Integer studentId : studentIds) {
            int num = pairSize;
            while(num > 0) {
                Marker marker = queue.poll();
                if (!marker.studentId.equals(studentId)) {
                    PairingEntity pairingEntity = new PairingEntity();
                    pairingEntity.setAssignmentId(assignmentId);
                    pairingEntity.setStudentId(studentId);
                    pairingEntity.setMarkerId(marker.studentId);
                    pairingEntity.setActive(0);
                    marker.count--;
                    num--;
                    pairs.add(pairingEntity);
                }
                if (marker.count > 0) {
                    queue.add(marker);
                }
            }
        }
        // save to DB
        Boolean b = this.saveBatch(pairs);
        return b;
    }

    @Override
    public List<PairingVO> listPairsOfAssignment(Integer assignmentId) {
        List<PairingEntity> pairingEntities = this.list(new QueryWrapper<PairingEntity>().eq("assignment_id", assignmentId));
        return pairingEntities.stream().map(pairingEntity -> {
            PairingVO pairingVO = new PairingVO();
            BeanUtils.copyProperties(pairingEntity, pairingVO);
            return pairingVO;
        }).collect(Collectors.toList());
    }

    @Override
    public List<PairingEntity> listPairsByMarkerId(Integer studentId) {
        return this.list(new QueryWrapper<PairingEntity>().eq("marker_id", studentId));
    }

    @Override
    public List<PairingEntity> listPairsByMarkerIdInactive(Integer studentId, Integer active) {
        return this.list(new QueryWrapper<PairingEntity>().eq("marker_id", studentId).and(wrapper -> wrapper.eq("active", active)));
    }

    @Override
    public PairingEntity getStudentIdByPairId(Integer pairId) {
        return pairingDao.selectOne(new QueryWrapper<PairingEntity>().eq("id", pairId));
    }

    @Override
    public List<PairingEntity> listPairsByStuAndAsgId(Integer stuId, Integer assignmentId) {
        return this.list(new QueryWrapper<PairingEntity>().eq("student_id", stuId).eq("assignment_id", assignmentId));
    }

    @Override
    public List<PairingEntity> listPairsByMarkerAndAsgId(Integer stuId, Integer assignmentId) {
        return this.list(new QueryWrapper<PairingEntity>().eq("marker_id", stuId).eq("assignment_id", assignmentId));
    }

    @Override
    public boolean inactivePair(List<Integer> submissionIds) {
        List<SubmissionEntity> submissionEntities = submissionDao.selectList(new QueryWrapper<SubmissionEntity>().in("id", submissionIds));
        if (CollectionUtils.isEmpty(submissionEntities)) {
            return true;
        }
        boolean allSuccess = true;
        for (SubmissionEntity submissionEntity : submissionEntities) {
            Integer studentId = submissionEntity.getSubmittedByStudentId();
            Integer assignmentId = submissionEntity.getAssignmentId();
            List<PairingEntity> pairingEntities = pairingDao.selectList(new QueryWrapper<PairingEntity>().eq("assignment_id", assignmentId).eq("student_id", studentId));
            for (PairingEntity pairingEntity : pairingEntities) {
                pairingEntity.setActive(0);
                boolean success = pairingDao.updateById(pairingEntity) > 0;
                allSuccess = success & allSuccess;
            }
        }
        return allSuccess;
    }

    @AllArgsConstructor
    @Data
    private class Marker {
        private Integer studentId;
        private Integer count;
    }

}
